﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.Collections;
using System.CodeDom.Compiler;
using System.IO;

namespace NBusiness.Templates
{
    public static class CodeNamespaceExtensions
    {
        #region CodeDomProvider

        public static string ToCode(this CodeDomProvider provider, CodeCompileUnit unit)
        {
            StringBuilder sb = new StringBuilder();

            using (TextWriter writer = new StringWriter(sb))
            {
                provider.GenerateCodeFromCompileUnit(unit, writer, new CodeGeneratorOptions());
            }

            return sb.ToString();
        }

        #endregion

        #region CodeCompileUnit extensions

        public static CodeNamespace Namespace(this CodeCompileUnit unit, string name)
        {
            CodeNamespace n = new CodeNamespace(name);
            unit.Namespaces.Add(n);
            return n;
        }
        #endregion

        #region CodeNamespace extensions

        public static CodeNamespace Import(this CodeNamespace self, params string[] imports)
        {
            foreach (string import in imports)
            {
                self.Imports.Add(new CodeNamespaceImport(import));
            }
            return self;
        }

        public static CodeTypeDeclaration Declare(this CodeNamespace self, string typeName)
        {
            CodeTypeDeclaration declaration = new CodeTypeDeclaration(typeName);
            self.Types.Add(declaration);

            return declaration;
        } 

        #endregion

        #region CodeTypeMember extensions

        public static CodeAttributeDeclaration Define(this CodeTypeMember member, Type attributeType)
        {
            CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(attributeType.Reference());
            member.CustomAttributes.Add(attribute);
            return attribute;
        }

        #endregion

        #region CodeAttributeDeclaration

        public static CodeAttributeDeclaration Parameter(
            this CodeAttributeDeclaration attribute,
            CodeExpression value)
        {
            attribute.Arguments.Add(new CodeAttributeArgument(value));
            return attribute;
        }

        public static CodeAttributeDeclaration Parameter(
            this CodeAttributeDeclaration attribute, 
            string name, 
            CodeExpression value)
        {
            attribute.Arguments.Add(new CodeAttributeArgument(name, value));
            return attribute;
        }

        #endregion

        #region CodeTypeDeclaration extensions

        public static CodeTypeDeclaration Implements(this CodeTypeDeclaration self, Type baseType)
        {
            self.BaseTypes.Add(new CodeTypeReference(baseType));
            return self;
        }

        public static CodeMemberMethod Method(this CodeTypeDeclaration type, string name)
        {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = name;
            type.Members.Add(method);
            return method;
        }

        #endregion

        #region IList extensions

        public static T Wrap<T>(this T members, string text) where T : IList
        {
            if (members != null && members.Count > 0)
            {
                CodeTypeMember start = members[0] as CodeTypeMember;
                CodeTypeMember end = members[members.Count - 1] as CodeTypeMember;

                if (start == null || end == null)
                    throw new ArgumentException("Collection must contain CodeTypeMember", "members");

                start.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, text));
                end.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, null));
            }

            return members;
        }

        #endregion

        #region CodeFieldMemberField extensions

        public static CodeFieldReferenceExpression Reference(this CodeMemberField member)
        {
            return Reference(member, null);
        }

        public static CodeFieldReferenceExpression Reference(this CodeMemberField member, CodeExpression target)
        {
            return new CodeFieldReferenceExpression(
                target,
                member.Name);
        } 

        #endregion

        #region CodeFieldReferenceExpression extensions

        public static CodeBinaryOperatorExpression NotEqualTo(this CodeFieldReferenceExpression field, object value)
        {
            CodeBinaryOperatorExpression binary = new CodeBinaryOperatorExpression();
            binary.Operator = CodeBinaryOperatorType.IdentityInequality;
            binary.Left = field;
            
            CodeExpression expression = value as CodeExpression;
            if(expression != null)
            {
                binary.Right = expression;
            }
            else binary.Right = new CodePrimitiveExpression(value);

            return binary;
        }

        public static CodeAssignStatement Assign(this CodeFieldReferenceExpression field, object value)
        {
            CodeAssignStatement assign = new CodeAssignStatement();
            assign.Left = field;

            CodeExpression expression = value as CodeExpression;
            if (expression != null)
            {
                assign.Right = expression;
            }
            else assign.Right = new CodePrimitiveExpression(value);
            
            return assign;
        }

        #endregion

        #region CodeMemberProperty extensions

        public static CodePropertySetValueReferenceExpression SetValue(this CodeMemberProperty member)
        {
            return new CodePropertySetValueReferenceExpression();
        }

        public static CodePropertyReferenceExpression Reference(this CodeMemberProperty member)
        {
            return Reference(member, null);
        }

        public static CodePropertyReferenceExpression Reference(this CodeMemberProperty member, CodeExpression target)
        {
            return new CodePropertyReferenceExpression(
                target,
                member.Name);
        }

        public static CodeStatementCollection Get(this CodeMemberProperty member)
        {
            return member.GetStatements;
        }

        public static CodeStatementCollection Set(this CodeMemberProperty member)
        {
            return member.SetStatements;
        } 

        #endregion

        #region CodeStatementCollection extensions

        public static CodeMethodReturnStatement Return(this CodeStatementCollection statements, CodeExpression expression)
        {
            CodeMethodReturnStatement ret = new CodeMethodReturnStatement(expression);
            statements.Add(ret);
            return ret;
        }

        public static CodeConditionStatement If(this CodeStatementCollection statements, CodeExpression condition)
        {
            CodeConditionStatement ifStatement = new CodeConditionStatement(condition);
            statements.Add(ifStatement);
            return ifStatement;
        }

        #endregion

        #region CodeConditionStatement extensions

        public static CodeConditionStatement Then(this CodeConditionStatement statement, params CodeObject[] statements)
        {
            foreach (CodeObject code in statements)
            {
                CodeExpression expression = code as CodeExpression;
                if (expression != null)
                {
                    statement.TrueStatements.Add(expression);
                }
                else
                {
                    CodeStatement s = code as CodeStatement;
                    if (s != null)
                    {
                        statement.TrueStatements.Add(s);
                    }
                    else
                    {
                        throw new ArgumentException("May only contain CodeExpression or CodeStatement objects");
                    }
                }
            }

            return statement;
        }

        public static CodeConditionStatement Else(this CodeConditionStatement statement)
        {
            return Else(statement, null);
        }

        public static CodeConditionStatement Else(this CodeConditionStatement statement, CodeExpression condition)
        {
            CodeConditionStatement elseStatement = new CodeConditionStatement(condition);
            statement.FalseStatements.Add(elseStatement);
            return elseStatement;
        }

        #endregion

        #region CodeMemberMethod

        public static CodeParameterDeclarationExpression Parameter(this CodeMemberMethod method, Type type, string name)
        {
            CodeParameterDeclarationExpression parameter = new CodeParameterDeclarationExpression(
                type.Reference(),
                name);

            method.Parameters.Add(parameter);
            return parameter;
        }

        public static CodeVariableDeclarationStatement Declare(
            this CodeMemberMethod method,
            Type type,
            string name,
            CodeExpression init)
        {
            CodeVariableDeclarationStatement variable = new CodeVariableDeclarationStatement(
                type, 
                name, 
                init);
            method.Statements.Add(variable);
            return variable;
        }

        public static CodeMethodReturnStatement Return(this CodeMemberMethod method, CodeExpression expression)
        {
            CodeMethodReturnStatement ret = new CodeMethodReturnStatement(expression);
            method.Statements.Add(ret);
            return ret;
        }

        #endregion

        #region CodeMethodReferenceExpression extensions

        public static CodeMethodInvokeExpression Invoke(this CodeMethodReferenceExpression method, params object[] parameters)
        {
            List<CodeExpression> expressions = new List<CodeExpression>();
            foreach (object parameter in parameters)
            {
                CodeExpression expression = parameter as CodeExpression;
                if (expression != null)
                {
                    expressions.Add(expression);
                }
                else
                {
                    CodeObject codeObject = parameter as CodeObject;
                    if (codeObject != null)
                    {
                        throw new ArgumentException("parameters must either be simple types or CodeExpressions");
                    }
                    else
                    {
                        expressions.Add(new CodePrimitiveExpression(parameter));
                    }
                }
            }

            return new CodeMethodInvokeExpression(
                method,
                expressions.ToArray());
        }

        #endregion

        #region CodeParameterDeclarationExpression

        public static CodeVariableReferenceExpression Reference(this CodeParameterDeclarationExpression parameter)
        {
            return new CodeVariableReferenceExpression(parameter.Name);
        }

        #endregion

        #region CodeVariableDeclarationStatement

        public static CodeVariableReferenceExpression Reference(this CodeVariableDeclarationStatement variable)
        {
            return new CodeVariableReferenceExpression(variable.Name);
        }

        #endregion

        #region CodeExpression

        public static CodePropertyReferenceExpression Property(this CodeExpression expression, string name)
        {
            return new CodePropertyReferenceExpression(
                expression,
                name);
        }

        #endregion

        #region string extensions

        public static string ToPascalCase(this string self)
        {
            string pascal = null;
            if (!string.IsNullOrEmpty(self))
            {
                pascal = self[0].ToString().ToUpper();
                if (self.Length > 1)
                    pascal += self.Substring(1);
            }

            return pascal;
        }

        public static string ToCamelCase(this string self)
        {
            string camel = null;
            if (!string.IsNullOrEmpty(self))
            {
                camel = self[0].ToString().ToLower();
                if (self.Length > 1)
                    camel += self.Substring(1);
            }

            return camel;
        } 

        #endregion

        #region Type

        public static CodeTypeReference Reference(this Type type)
        {
            return new CodeTypeReference(type);
        }

        public static CodeTypeReferenceExpression Expression(this Type type)
        {
            return new CodeTypeReferenceExpression(type);
        }

        #endregion
    }
}
